﻿// ResXScriptGenerator.cs
// Script#/Tools/VisualStudio
// Copyright (c) Nikhil Kothari.
// Copyright (c) Microsoft Corporation.
// This source code is subject to terms and conditions of the Microsoft 
// Public License. A copy of the license can be found in License.txt.
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

namespace ScriptSharp.VisualStudio.Generators {

    public abstract class ResXScriptGenerator : Interop.IObjectWithSite, Interop.IVsSingleFileGenerator {

        private object _site;
        private bool _publicResources;

        protected ResXScriptGenerator(bool publicResources) {
            _publicResources = publicResources;
        }

        public string GenerateCode(string resourceFileName, string resourceFileContent, string namespaceName) {
            if (IsLocalizedResourceFile(resourceFileName)) {
                return null;
            }

            List<ResXItem> resourceItems = ResXParser.ParseResxMarkup(resourceFileContent);
            if (resourceItems.Count == 0) {
                return null;
            }

            string className = Path.GetFileNameWithoutExtension(resourceFileName);

            StringBuilder codeBuilder = new StringBuilder(2048);

            codeBuilder.AppendLine("//------------------------------------------------------------------------------");
            codeBuilder.AppendLine("// <auto-generated>");
            codeBuilder.Append("// ");
            codeBuilder.Append(className);
            codeBuilder.AppendLine(".cs");
            codeBuilder.AppendLine("//");
            codeBuilder.Append("// Do not edit directly. This file has been generated by ");
            codeBuilder.Append(GetGeneratorName());
            codeBuilder.AppendLine(".");
            codeBuilder.AppendLine("// </auto-generated>");
            codeBuilder.AppendLine("//------------------------------------------------------------------------------");
            codeBuilder.AppendLine();
            codeBuilder.AppendLine("using System;");
            codeBuilder.AppendLine("using System.CodeDom.Compiler;");
            codeBuilder.AppendLine("using System.Runtime.CompilerServices;");
            codeBuilder.AppendLine();
            codeBuilder.Append("namespace ");
            codeBuilder.Append(namespaceName);
            codeBuilder.AppendLine(" {");
            codeBuilder.AppendLine();
            codeBuilder.Append("    /// <summary>");
            codeBuilder.Append(className);
            codeBuilder.AppendLine(" resources class</summary>");
            codeBuilder.Append("    [GeneratedCodeAttribute(\"");
            codeBuilder.Append(this.GetType().Name);
            codeBuilder.Append("\", \"");
            codeBuilder.Append(typeof(ResXScriptGenerator).Assembly.GetName().Version.ToString());
            codeBuilder.AppendLine("\")]");
            codeBuilder.AppendLine("    [Resources]");
            codeBuilder.Append(_publicResources ? "    public" : "    internal");
            codeBuilder.Append(" static class ");
            codeBuilder.Append(className);
            codeBuilder.AppendLine(" {");

            foreach (ResXItem resourceItem in resourceItems) {
                codeBuilder.AppendLine();
                if (resourceItem.Comment.Length != 0) {
                    codeBuilder.Append("        /// <summary>");
                    codeBuilder.Append(resourceItem.Comment);
                    codeBuilder.AppendLine("</summary>");
                }
                codeBuilder.Append("        public static readonly string ");
                codeBuilder.Append(resourceItem.Name);
                codeBuilder.AppendLine(" = null;");
            }

            codeBuilder.AppendLine("    }");
            codeBuilder.AppendLine("}");

            return codeBuilder.ToString();
        }

        private string GetGeneratorName() {
            string format;
            if (_publicResources) {
                format = "ResXPublicScriptGenerator v{0}";
            }
            else {
                format = "ResXInternalScriptGenerator v{0}";
            }

            Version versionInfo = typeof(ResXScriptGenerator).Assembly.GetName().Version;
            return String.Format(format, versionInfo);
        }

        private static bool IsLocalizedResourceFile(string resourceFileName) {
            string locale = ResourceFile.GetLocale(resourceFileName);
            return (String.IsNullOrEmpty(locale) == false);
        }

        #region Implementation of IObjectWithSite
        int Interop.IObjectWithSite.SetSite(object pUnkSite) {
            _site = pUnkSite;
            return Interop.S_OK;
        }

        int Interop.IObjectWithSite.GetSite(ref Guid riid, out IntPtr ppunkSite) {
            ppunkSite = IntPtr.Zero;

            int hr = Interop.E_FAIL;

            if (_site != null) {
                IntPtr punk = Marshal.GetIUnknownForObject(_site);
                if (punk != IntPtr.Zero) {
                    try {
                        hr = Marshal.QueryInterface(punk, ref riid, out ppunkSite);
                    }
                    finally {
                        Marshal.Release(punk);
                        punk = IntPtr.Zero;
                    }
                }
            }

            return hr;
        }
        #endregion

        #region IVsSingleFileGenerator Members
        int Interop.IVsSingleFileGenerator.GetDefaultExtension(out string extension) {
            extension = ".cs";
            return Interop.S_OK;
        }

        int Interop.IVsSingleFileGenerator.Generate(string wszInputFilePath, string bstrInputFileContents, string wszDefaultNamespace, out IntPtr pbstrOutputFileContents, out int pbstrOutputFileContentsSize, Interop.IVsGeneratorProgress pGenerateProgress) {
            pbstrOutputFileContents = IntPtr.Zero;
            pbstrOutputFileContentsSize = 0;

            if (String.IsNullOrEmpty(wszInputFilePath)) {
                return Interop.E_INVALIDARG;
            }
            if (String.IsNullOrEmpty(bstrInputFileContents)) {
                return Interop.E_INVALIDARG;
            }

            string code = null;

            try {
                code = GenerateCode(wszInputFilePath, bstrInputFileContents, wszDefaultNamespace);
            }
            catch (Exception e) {
                Debug.Fail(e.ToString());
            }

            if (String.IsNullOrEmpty(code)) {
                return Interop.E_FAIL;
            }

            try {
                byte[] codeBytes = Encoding.UTF8.GetBytes(code);

                IntPtr buffer = Marshal.AllocCoTaskMem(codeBytes.Length);
                Marshal.Copy(codeBytes, 0, buffer, codeBytes.Length);

                pbstrOutputFileContents = buffer;
                pbstrOutputFileContentsSize = codeBytes.Length;
            }
            catch {
                return Interop.E_OUTOFMEMORY;
            }

            return Interop.S_OK;
        }
        #endregion
    }
}
